5. Creating a New Drawable Class
Once again, create a new class in Xcode, and name it TextDrawingInfo. The code for this is pretty similar to the PathDrawingInfo class from this article,
but here we're keeping track of a slightly different set of details and
providing a different set of methods for creating new instances. TextDrawingInfo.h looks like this:
// TextDrawingInfo.h
#import <Foundation/Foundation.h>
#import "Drawable.h"
@interface TextDrawingInfo : NSObject <Drawable> {
UIBezierPath *path;
UIColor *strokeColor;
UIFont *font;
NSString *text;
}
@property (retain, nonatomic) UIBezierPath *path;
@property (retain, nonatomic) UIColor *strokeColor;
@property (retain, nonatomic) UIFont *font;
@property (copy, nonatomic) NSString *text;
- (id)initWithPath:(UIBezierPath*)p text:(NSString*)t strokeColor:(UIColor*)s font:(UIFont*)f;
+ (id)textDrawingInfoWithPath:(UIBezierPath *)p text:t strokeColor:(UIColor *)s font:(UIFont *)f;
@end
As for the implementation, TextDrawingInfo.m is pretty straightforward. The only interesting method is the draw method. Here's the whole thing:
// TextDrawingInfo.m
#import "TextDrawingInfo.h"
#import <CoreText/CoreText.h>
@implementation TextDrawingInfo
@synthesize path, strokeColor, font, text;
- initWithPath:(UIBezierPath*)p text:(NSString*)t strokeColor:(UIColor*)s font:(UIFont*)f {
if ((self = [self init])) {
path = [p retain];
strokeColor = [s retain];
font = [f retain];
text = [t copy];
}
return self;
}
+ (id)textDrawingInfoWithPath:(UIBezierPath *)p text:t strokeColor:(UIColor *)s font:(UIFont *)f {
return [[[self alloc] initWithPath:p text:t strokeColor:s font:f] autorelease];
}
- (void)dealloc {
self.path = nil;
self.strokeColor = nil;
self.font = nil;
self.text = nil;
[super dealloc];
}
- (void)draw {
CGContextRef context = UIGraphicsGetCurrentContext();
NSMutableAttributedString *attrString =
[[[NSMutableAttributedString alloc] initWithString:self.text] autorelease];
[attrString addAttribute:(NSString *)(kCTForegroundColorAttributeName)
value:(id)self.strokeColor.CGColor
range:NSMakeRange(0, [self.text length])];
CTFramesetterRef framesetter =
CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, [attrString length]),
self.path.CGPath, NULL);
CFRelease(framesetter);
if (frame) {
CGContextSaveGState(context);
// Core Text wants to draw our text upside down! This flips it the
// right way.
CGContextTranslateCTM(context, 0, path.bounds.origin.y);
CGContextScaleCTM(context, 1, −1);
CGContextTranslateCTM(context, 0, -(path.bounds.origin.y + path.bounds.size.height));
CTFrameDraw(frame, context);
CGContextRestoreGState(context);
CFRelease(frame);
}
}
@end
You may recognize some pieces of the draw
method. We create an attributed string and set its color, use it to
create a CTFramesetterRef, and then use that to create a CTFrameRef. Here, we've added a check to make sure the CTFrameRef is created and also a few CGContext
function calls. These are here for a specific reason: On the Mac OS X
platform, the y axis is flipped (relative to the way it's done on iOS)
for normal drawing. Core Text, which comes from Mac OS X, expects that
flipped axis, which means that when it draws in an iOS context, the
results are upside down! The translate/scale/translate triad in the
preceding code makes sure that the text appears right side up.
Before this will compile,
we need to add the Core Text framework to the Xcode project. Right-click
the Frameworks folder, select Add => Existing Frameworks... from the context menu, select CoreText.framework from the list that appears, and then click the Add button.
Now that everything is in
place, you should be able to build and run Dudel in the simulator, and
use the new Text tool to add text to your drawings. The masterpiece on
display in Figure 5-5 just scratches the surface of what can be done here.